#include <cfloat>

#include <QtGui>
#include <QVariant>

#include "mainwindow.h"
#include "messagedisplay.h"
#include "setviewerpanel.h"
#include "datacontroller.h"


MainWindow::MainWindow(QWidget *parent,
                       Qt::WindowFlags flags,
                       SystemControl *system) :
        QMainWindow(parent, flags),
        m_system(system)
{
    this->setWindowTitle("Fuzzy Logic Control Center");

    m_mainWidget = new MainWidget(this, system);
    this->setCentralWidget(m_mainWidget);

    m_menubar = new MenuBar(this);
    this->setMenuBar(m_menubar);
    this->connectMenuBar();
    this->updateRecentFiles();

    m_options = new OptionPane;

    this->resize(800, 600);
    this->setAttribute(Qt::WA_QuitOnClose);
}

void MainWindow::init()
{
    /* If a file has been opened before, load it. */
    if(!m_recent_files.isEmpty())
        this->openScenario(m_recent_files.front());
    else
    {
        this->openScenario(":/Example.xml");
    }
}

QString MainWindow::getOctavePath()
{
    return m_options->getOctavePath().simplified();
}

int MainWindow::getOctaveResolution()
{
    return m_options->getOctaveResolution();
}

void MainWindow::connectMenuBar()
{
    connect(m_menubar->create, SIGNAL(triggered()), this, SLOT(newScenario()));
    connect(m_menubar->open, SIGNAL(triggered()), this, SLOT(openScenario()));
    connect(m_menubar->save, SIGNAL(triggered()), this, SLOT(saveScenario()));

    connect(m_menubar->options, SIGNAL(triggered()),
            this, SLOT(optionWindow()));

    connect(m_menubar->about, SIGNAL(triggered()), this, SLOT(aboutWindow()));
}

void MainWindow::newScenario()
{
    /* I don't really know what's going to go here yet. */
    /* Probably want some options window to show up? */
}

void MainWindow::openScenario()
{
    QAction *action = qobject_cast<QAction *>(sender());

    QString fileName;

    for(int i = 0; i < m_menubar->files.size(); ++i)
        if (action == m_menubar->files[i])
            fileName = action->text();

    if( fileName.isNull() )
        fileName = QFileDialog::getOpenFileName(0, "Select a scenario file");

    /* Safety check */
    if ( fileName == "" )
        return;

    if(!m_recent_files.contains(fileName)) {
        m_recent_files.push_front(fileName);
    }
    /* If the file is already in the array, make sure that it's at the front */
    else {
        m_recent_files.removeAt(m_recent_files.indexOf(fileName));
        m_recent_files.push_front(fileName);
    }

    this->openScenario(fileName);

    this->writeRecentFiles();
    this->updateRecentFiles();
}

void MainWindow::openScenario(const QString &fileName)
{
    qDebug() << fileName;
    DataController::instance()->loadDataFromFile(fileName);
    /*Function to update the lineEdits after they have been initialized */

    m_mainWidget->getToolkit()->resetInputOneFuzzy();
    m_mainWidget->getToolkit()->resetInputTwoFuzzy();

    this->repaint();
}

void MainWindow::saveScenario()
{
    /* File dialog */
    QString fileName = QFileDialog::getSaveFileName(0,
                                                    "Saving Output",
                                                    "",
                                                    "XML File (*.xml)");

    /* Safety check */
    if ( fileName == "" )
        return;

    DataController::instance()->saveDataToFile(fileName);
}

void MainWindow::aboutWindow()
{
    QFile about(":/About.txt");
    about.open(QIODevice::ReadOnly | QIODevice::Text);

    QString aboutText = about.readAll();

    QMessageBox::about(this, "About", aboutText);
}

void MainWindow::optionWindow()
{
    m_options->show();
}

void MainWindow::updateRecentFiles()
{
    QFile recentFiles("RecentDocuments.txt");

    recentFiles.open(QIODevice::ReadOnly | QIODevice::Text);

    while(!recentFiles.atEnd()) {
        QString line = recentFiles.readLine();

        /* Ensures that the last character isn't a new line, which creates
         * issues.
         */
        if(line.endsWith("\n"))
            line.truncate(line.size() - 1);

        /* If the file no longer exists, Don't try to add it to the array,
         * because it will cause a crash.
         */
        if(!QFile::exists(line)) continue;

        /* If the file doesn't exist in the array, add it */
        if(!m_recent_files.contains(line))
            if(!line.isNull())
                m_recent_files.push_back(line);
    }

    m_menubar->updateRecentFiles(m_recent_files);

    for(int i = 0; i < m_menubar->files.size(); ++i) {
        connect(m_menubar->files[i], SIGNAL(triggered()),
                this, SLOT(openScenario()));
    }
}

void MainWindow::writeRecentFiles()
{
    QFile file("RecentDocuments.txt");

    if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
        qDebug() << "There was a fail. Mainwindow::writeRecentFiles()";

    for(int i = 0; i < m_recent_files.size(); ++i) {
        file.write( m_recent_files[i].toAscii() );
        if(i != m_recent_files.size() - 1)
            file.write("\n");
    }
}


/******************************* MainWidget **********************************/

MainWindow::MainWidget::MainWidget(QWidget *parent, SystemControl *systemC) :
    QWidget(parent),
    m_system(systemC)
{
    /* Set up the primary layout of the main window */
    QHBoxLayout *mainLayout = new QHBoxLayout(this);

    /* At the top left of the main window, we'll have the two input sets: */
    QGroupBox *inputGroup = new QGroupBox("Fuzzy input sets");

    /* Get a DataController instance */
    DataController *system = DataController::instance();

    /* Set up the input set viewers. */
    SetViewerPanel *inputSetOne = new SetViewerPanel(&system->getInputOne(),
                                                     this);
    SetViewerPanel *inputSetTwo = new SetViewerPanel(&system->getInputTwo(),
                                                     this);

    /* Make a vertical layout for the two input sets so that they are laid out
     * on top of the other, add them to the input group window.
     */
    QVBoxLayout *inputLayout = new QVBoxLayout;
    inputLayout->addWidget(inputSetOne);
    inputLayout->addWidget(inputSetTwo);
    inputGroup->setLayout(inputLayout);

    /* Underneath the input sets, we'll be able to see the output set */
    QGroupBox *outputGroup = new QGroupBox("Fuzzy output set");

    /* Set up the thing that shows the output fuzzy set. */
    SetViewerPanel *outputSet = new SetViewerPanel(&system->getOutput(),
                                                   this);

    /* Set up the output set layout and add it to the output group */
    QVBoxLayout *outputLayout = new QVBoxLayout;
    outputLayout->addWidget(outputSet);
    outputGroup->setLayout(outputLayout);

    /* Set up the layout for these two boxes in a vertical fashion and add
     * them to the main window.  The input group is 2 times the size of the
     * output group because there are two sets in there.
     */
    QVBoxLayout *groupViewer = new QVBoxLayout;
    groupViewer->addWidget(inputGroup, 2);
    groupViewer->addWidget(outputGroup, 1);

    /* Beside the set viewer, there will be something on the side... I don't
     * think we really know what's going here yet, but we can have some room
     * set aside for whatever we decide we want to have here.
     */
    QGroupBox *toolBox = new QGroupBox("Toolbox");

    /* Create a vertical layout for the toolbox */
    QVBoxLayout *toolLayout = new QVBoxLayout;
    toolBox->setLayout(toolLayout);

    /* Create a new toolkit object and add it to the tool layout. */
    toolkit = new Toolkit;

    toolLayout->addLayout(toolkit);

    /* Connect each of the three text boxes to the slot that they are
     * associated with. Connect the signals that affect the textboxes.
     */
    connect(toolkit->inputA, SIGNAL(textChanged(QString)),
            this, SLOT(setInputOneFuzzy(QString)));

    connect(inputSetOne, SIGNAL(lineChanged()), toolkit,
            SLOT(resetInputOneFuzzy()));

    connect(toolkit->inputB, SIGNAL(textChanged(QString)),
            this, SLOT(setInputTwoFuzzy(QString)));

    connect(inputSetTwo, SIGNAL(lineChanged()), toolkit,
            SLOT(resetInputTwoFuzzy()));

    connect(this, SIGNAL(outputChanged(QString)),
            toolkit->output, SLOT(setText(QString)));

    toolLayout->addStretch();

    /* Create the button group and add it to the main layout. */
    ButtonGroup *buttons = new ButtonGroup();

    /* Connect all of the buttons to their associated slots. */
    connect(buttons->editor, SIGNAL(clicked()), this, SLOT(showEditor()));
    connect(buttons->matrix, SIGNAL(clicked()), this, SLOT(showMatrix()));
    connect(buttons->surface, SIGNAL(clicked()), this, SLOT(showSurface()));
    connect(buttons->quit, SIGNAL(clicked()), qApp, SLOT(quit()));

    /* Layout the right side of the main window */
    QVBoxLayout *rightLayout = new QVBoxLayout;
    rightLayout->addWidget(toolBox);
    rightLayout->addLayout(buttons);

    /* Set up the main window so that the set veiwers are quite a bit larger
     * than the toolbox so that it looks good
     */
    mainLayout->addLayout(groupViewer, 3);
    mainLayout->addLayout(rightLayout, 1);

    /* Set the layout of the main window to be the layout we just made and size
     * it so that it's a nice size.
     */
    this->setLayout(mainLayout);
}

void MainWindow::MainWidget::showEditor()
{
    m_system->getEditorWindow()->show();
}

void MainWindow::MainWidget::showMatrix()
{
    if ( DataController::instance()->getRuleListSize() == 0 ) {
//        (new MessageDisplay("No rules have been set yet",
//                            "Matrix Editor Error"))->show();
//        return;
    }

    m_system->getMatrixWindow()->init();
    m_system->getMatrixWindow()->show();
}

void MainWindow::MainWidget::showSurface() {

    /* Another situation where at this point, I really don't know how to
     * continue with this function, but it will require its own class and
     * stuff.  We'll see how things progress.
     */
    m_system->getFuzzyController()->get3Doutput();
}

void MainWindow::MainWidget::setInputOneFuzzy(QString value) {

    DataController::instance()->setInputOneFuzzyValues(value.toDouble());

    this->checkOutput();
    repaint();

}

void MainWindow::MainWidget::setInputTwoFuzzy(QString value) {

    DataController::instance()->setInputTwoFuzzyValues(value.toDouble());

    this->checkOutput();
    repaint();
}


void MainWindow::MainWidget::checkOutput() {

    DataController *data = DataController::instance();

    double input1 = data->getInputOneInputValue();
    double input2 = data->getInputTwoInputValue();

    double output = m_system->getFuzzyController()->getCrispOutput(input1,
                                                                   input2);
    QString outputText;
    outputText.setNum(output);

    emit outputChanged(outputText);
}

/******************************** Toolkit ************************************/

MainWindow::Toolkit::Toolkit()
{
    /* Create a validator that ensures that the values entered are doubles
     * and that they have only 5 decimal places
     */
    QDoubleValidator* validator = new QDoubleValidator(-DBL_MAX,
                                                       DBL_MAX,
                                                       5,
                                                       this);

    /* Create the three labels and textboxes that the toolbox consists of.
     * Each component requires a validator.  The output text field is disabled
     * because the system controls its contents, not the user
     */
    this->addWidget(new QLabel("Input 1"));
    inputA = new QLineEdit;
    inputA->setValidator(validator);
    resetInputOneFuzzy();
    this->addWidget(inputA);

    this->addWidget(new QLabel("Input 2"));
    inputB = new QLineEdit;
    inputB->setValidator(validator);
    resetInputTwoFuzzy();
    this->addWidget(inputB);

    this->addWidget(new QLabel("Output"));
    output = new QLineEdit;
    output->setValidator(validator);
    output->setEnabled(false);
    this->addWidget(output);
}

void MainWindow::Toolkit::resetInputOneFuzzy()
{    
    QString text;
    text.setNum(DataController::instance()->getInputOneInputValue(), 'f', 2);
    inputA->setText(text);

}

void MainWindow::Toolkit::resetInputTwoFuzzy()
{
    QString text;
    text.setNum(DataController::instance()->getInputTwoInputValue(), 'f', 2);
    inputB->setText(text);
}


/****************************** ButtonGroup **********************************/

MainWindow::ButtonGroup::ButtonGroup()
{
    /* Add a button for opening up the fuzzy editor */
    editor = new QPushButton("Set Editor");
    editor->setToolTip("Display the set editor window");
    this->addWidget(editor);

    /* Set up a button that lets the user see the output matrix:
     */
    matrix = new QPushButton("Output Matrix");
    matrix->setToolTip("View and edit the output matrix");
    this->addWidget(matrix);

    /* Set up a button that lets the user see the 3D surface that the input and
     * output sets produce:
     */
    surface = new QPushButton("3D Surface");
    surface->setToolTip("View the surface produced by these sets");
    this->addWidget(surface);

    /* Set up a button to let the user quit the application, although really
     * we probably won't keep this since this wont be a derpy program
     * (hopefully), we should have this kind of functionality built into some
     * kind of title bar thing.
     */
    quit = new QPushButton("Exit");
    quit->setToolTip("Exit the program");
    this->addWidget(quit);
}

/******************************** MenuBar ************************************/

MainWindow::MenuBar::MenuBar(QWidget *parent) :
        QMenuBar(parent)
{
    /* This class can be counter intuitive at times.  Certain menus do not
     * appear on different platforms because of the operating system's
     * standards.  In Mac OS X, the "About" menu option is placed in the
     * program's main menu.  It seems as though at compilation these menus are
     * sorted into the places that the operating system thinks that they should
     * be in
     */
    file = new QMenu("File", this);
    edit = new QMenu("Edit", this);
    help = new QMenu("Help", this);

    create = file->addAction("New Scenario...");
    create->setShortcuts(QKeySequence::New);
    create->setStatusTip("Create a new scenario from scratch");

    open = file->addAction("Load Scenario...");
    open->setShortcuts(QKeySequence::Open);
    open->setStatusTip("Load a scenario from a file");

    file->addSeparator();

    save = file->addAction("Save Scenario...");
    save->setShortcuts(QKeySequence::Save);
    save->setStatusTip("Save the current scenario to a file");

    file->addSeparator();

    reset = edit->addAction("Reset Output");
    reset->setStatusTip("Reset the inputs and outputs to have no fuzzy value");

    edit->addSeparator();

    options = edit->addAction("Options...");
    options->setStatusTip("View and edit the options");

    about = help->addAction("About");

    this->addMenu(file);
    this->addMenu(edit);
    this->addMenu(help);
}

void MainWindow::MenuBar::updateRecentFiles(const QStringList &list)
{
    while( !files.isEmpty() ) {
        file->removeAction(files.back());
        delete files.back();
        files.pop_back();
    }

    for(int i = 0; i < list.size(); ++i) {
        QAction *temp = file->addAction(list[i]);
        files.push_front(temp);
    }
}

/******************************* OptionPane **********************************/

MainWindow::OptionPane::OptionPane(QWidget *parent):
        QWidget(parent)
{
    this->setWindowTitle("Options");

    /* I need to set this up so that the options file can be accessed as a
       dictionary rather than just picking up elements in an ad hoc manner.
       Using a dictionary lets the whole file be loaded with a single simple
       operation, and then we wont have to worry about loading things up
       individually like they have to right now.
     */
    QFile options("FileLocations.txt");

    /* Open the file read/write to create new files if it doesn't exist.     */
    if(!options.exists()){
        options.open(QIODevice::ReadWrite | QIODevice::Text);
        /* Should it be closed and then reopened? I presume so. Correct me if
         * I'm wrong.
         */
        options.close();
    }

    if(!options.open(QIODevice::ReadOnly | QIODevice::Text))
        qDebug() << "There was a fail: File not open.";

    QVBoxLayout *mainLayout = new QVBoxLayout(this);

    /*Record the strings that are in the QLineEdit, removing all non-printing
      characters.
      */
    QString octave = options.readLine().simplified();
    QString octaveRes = options.readLine().simplified();

    /* If we prematurely hit the end of the file, set the last value to 50.
       Using a dictionary structure we can avoid things like this.
     */
    if ( options.error() || octaveRes == "" )
    {
        octaveRes = "50";
    }
    if ( options.error() || octave == "")
    {
        octave = "octave";
    }

    QHBoxLayout *octavePathLine = new QHBoxLayout;
    octavePath = new QLineEdit(octave);
    octavePathLine->addWidget(new QLabel("Octave File Path:"));
    octavePathLine->addWidget(octavePath);

    QHBoxLayout *octaveResLine = new QHBoxLayout;
    octaveResolution = new QLineEdit(octaveRes);
    octaveResLine->addWidget(new QLabel("Octave Iterations:"));
    octaveResLine->addWidget(octaveResolution);

    /* Make sure that only ints can be entered */
    QIntValidator *valid = new QIntValidator(1, INT_MAX, this);
    octaveResolution->setValidator(valid);

    connect(octaveResolution, SIGNAL(editingFinished()),
            this, SLOT(rewriteOptionsFile()));
    connect(octavePath, SIGNAL(editingFinished()),
            this, SLOT(rewriteOptionsFile()));


    QPushButton *octaveBrowse = new QPushButton("Browse");
    connect(octaveBrowse, SIGNAL(clicked()), this, SLOT(findOctavePath()));
    octavePathLine->addWidget(octaveBrowse);

    options.close();
    mainLayout->addLayout(octavePathLine);
    mainLayout->addLayout(octaveResLine);
    this->setLayout(mainLayout);
}

void MainWindow::OptionPane::findOctavePath() {

    QString octave = QFileDialog::getOpenFileName(0, "Octave");

    if(octave == "") return;

    octavePath->setText(octave);

    this->rewriteOptionsFile();
}

void MainWindow::OptionPane::rewriteOptionsFile()
{
    QFile options("FileLocations.txt");

    if(!options.open(QIODevice::WriteOnly | QIODevice::Text))
        qDebug() << "There was a fail: Not possible to write file.";

    options.write(octavePath->text().toAscii());
    options.write("\n");
    options.write(octaveResolution->text().toAscii());

    options.close();
}
